﻿using Microsoft.Net.Http;
using Neudesic.Pulse.Entities;
using System;
using System.IO;
using System.Net;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

namespace Neudesic.Pulse
{

    /// <summary>
    /// Primary client class for interacting with the Pulse REST services.
    /// </summary>
    /// <remarks>
    /// This client is stateful and is meant to be used across calls (for a given user) for performance reasons.
    /// </remarks>
    public partial class PulseClient : IDisposable
    {
        
        private Lazy<Entity> currentUser = null;
        private string baseAddress = null;

        /// <summary>
        /// Gets the current user.
        /// </summary>
        /// <remarks>
        /// This is typically the primary entry point into the resource tree.
        /// </remarks>
        /// <value>The current user.</value>
        public Entity CurrentUser
        {
            get { return currentUser.Value; }
        }

        /// <summary>
        /// Gets the root service address.
        /// </summary>
        /// <remarks>
        /// Includes the "stream" fragment.
        /// </remarks>
        /// <value>The base address.</value>
        public string BaseAddress
        {
            get
            {
                return baseAddress;
            }
        }

        ICredentials credentials;
        void init(WebRequest request)
        {
            if (credentials == null)
            {
                request.UseDefaultCredentials = true;
                request.Credentials = System.Net.CredentialCache.DefaultCredentials;
            }
            else
            {
                request.Credentials = credentials;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PulseClient" /> class.
        /// </summary>
        /// <param name="baseAddress">The base address.</param>
        /// <param name="authenticationScheme">The authentication scheme.</param>
        public PulseClient(string baseServiceAddress, AuthenticationScheme authenticationScheme, ICredentials credential = null)
        {

            baseAddress = UriUtility.CombineUri(baseServiceAddress, "streams/");
            credentials = credential;
            
            // Need to attach security to the channel so that our credentials are passed
            
            // Define current user
            currentUser = new Lazy<Entity>(
                () => { return this.GetCurrentUser(); },
                System.Threading.LazyThreadSafetyMode.PublicationOnly
                );

        }

        /// <summary>
        /// Gets a raw response from the given Uri.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public HttpWebResponse Get(string uri)
        {
            WebRequest request = WebRequest.Create(uri);
            init(request);
            request.Method = "GET";            
            request.Headers.Add("X-Accept", MimeTypes.ApplicationJson);

            var response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK)
            {
                throw new RestClientException("Unable to process request");
            }
            return response;

        }

        /// <summary>
        /// Gets a resource located at the given Uri and returns a strongly typed object.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public T Get<T>(string uri)
        {
            return Get<T>(uri, null);
        }

        /// <summary>
        /// Gets a resource located at the given Uri and returns a strongly typed object using the token found at jPath as the root.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="jPath">The j path.</param>
        /// <returns></returns>
        public T Get<T>(string uri, string jPath)
        {
            WebRequest request = WebRequest.Create(uri);
            init(request);
            request.Method = "GET";
            request.Headers.Add("X-Accept", MimeTypes.ApplicationJson);

            var response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK)
            {
                throw new RestClientException("Unable to process request");
            }

            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                if (string.IsNullOrEmpty(jPath))
                    return JsonConvert.DeserializeObject<T>(reader.ReadToEnd());
                else
                {
                    // Grab the Json and find the JToken corresponding to the JPath
                    JObject jLinq = JObject.Parse(reader.ReadToEnd());
                    JToken root = jLinq.SelectToken(jPath, false);

                    if (root != null)
                    {
                        return JsonConvert.DeserializeObject<T>(root.ToString());
                    }
                    else
                    {
                        throw new RestClientException(string.Format("No token found at path {0}.", jPath)) { StatusCode = -1 };
                    }

                }
            }
                        
        }

        /// <summary>
        /// Posts an instance to a given Uri.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public string Post(object instance, string uri)
        {
            WebRequest request = WebRequest.Create(uri);
            init(request);
            request.Method = "POST";
            request.ContentType = MimeTypes.ApplicationJson;
            request.Headers.Add("X-Accept", MimeTypes.ApplicationJson);
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.DefaultValueHandling = DefaultValueHandling.Ignore;

            var requestStream = request.GetRequestStream();
            StreamWriter sw = new StreamWriter(requestStream);
            sw.Write(JsonConvert.SerializeObject(instance, Formatting.None, settings));
            sw.Flush();
            
            var response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
            {
                throw new RestClientException("Unable to process request");
            }

            using (var responseStream = response.GetResponseStream())
            {
                return new StreamReader(responseStream).ReadToEnd();
            }
        }

        /// <summary>
        /// Posts the specified buffer to a given Uri.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="uri">The URI.</param>
        /// <returns></returns>
        public string Post(byte[] buffer, string uri)
        {
            WebRequest request = WebRequest.Create(uri);
            init(request);
            request.Method = "POST";
            request.ContentType = MimeTypes.ApplicationJson;
            request.Headers.Add("X-Accept", MimeTypes.ApplicationJson);
            var requestStream = request.GetRequestStream();
            requestStream.Write(buffer, 0, buffer.Length);
            var response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
            {
                throw new RestClientException("Unable to process request");
            }

            using (var responseStream = response.GetResponseStream())
            {
                return new StreamReader(responseStream).ReadToEnd();
            }
        }

        /// <summary>
        /// Makes a DELETE call to a given Uri.
        /// </summary>
        /// <param name="uri">The URI.</param>
        public void Delete(string uri)
        {
            WebRequest request = WebRequest.Create(uri);
            init(request);
            request.Method = "DELETE";
            request.Headers.Add("X-Accept", MimeTypes.ApplicationJson);
            
            var response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK)
            {
                throw new RestClientException("Unable to process request");
            }            
        }

        /// <summary>
        /// Gets the entity that represents the current thread identity.
        /// </summary>
        /// <returns></returns>
        internal Entity GetCurrentUser()
        {
            return Get<Entity>(baseAddress + "my");            
        }

        #region Finalizer/IDisposable Members

        private bool m_disposed;

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="LogManager"/> is reclaimed by garbage collection.
        /// </summary>
        ~PulseClient()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // Call the overload and suppress finalization since we're already running the dispose.
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {

            if (!m_disposed)
            {
                if (disposing)
                {

                   
                }

                m_disposed = true;

            }

            // Clean up unmanaged resources here

        }
        #endregion

    }
}
